package farmer.update.building;

import farmer.game.Data;

import pathfinding.grid.GridLocation;
import pathfinding.grid.GridMap;
import pathfinding.grid.GridPath;
import pathfinding.grid.GridPathfinding;

import java.util.ArrayList;

import farmer.render.doodad.Rock;
import farmer.render.doodad.Tree;
import farmer.render.building.City;

import farmer.core.Basic;
import farmer.core.Building;
import farmer.core.BuildingRessource;
import farmer.core.Doodad;
import farmer.core.Unit;
import farmer.core.Updateable;

import farmer.update.unit.Transporter;

public  class Map extends Basic implements Updateable{
	
	public static final int cellWidth = 35;
	public static final int unitCellWidth = 7;
	
	protected City theCity;
	
	protected int [][] terrainMap;
	protected int sizeX;
	protected int sizeY;
	protected ArrayList<Doodad> doodadList = new ArrayList<Doodad>();
	protected ArrayList<Building> buildingList = new ArrayList<Building>();
	protected ArrayList<Unit> unitList = new ArrayList<Unit>();
	protected ArrayList<Unit> selectedUnitList = new ArrayList<Unit>();
	protected ArrayList<Building> selectedBuildingList = new ArrayList<Building>();
	
	public final static int empty = 0;
	public final static int full = 1;
	public final static int tree = 2;
	public final static int farm = 3;
	public final static int road = 4;
	public final static int rock = 5;
	public final static int city = 6;
	public final static int door = 7;
	public final static int door1 = 8;
	protected int [][] constructionMap;
	
	protected int [][] mouvementMap;
	GridPathfinding pathfinding = new GridPathfinding();
	
	public Map(int sizeX, int sizeY){
		map = (farmer.render.building.Map) this;
		this.sizeX = sizeX;
		this.sizeY = sizeY;
		terrainMap = new int[sizeX][sizeY];
		constructionMap = new int[sizeX][sizeY];
	}
	
	public ArrayList<Building> getBuildingList(){
		return buildingList;
	}
	
	public ArrayList<Unit> getUnitList(){
		return unitList;
	}
	
	public int getConstructionMapValue(int x, int y){
		return constructionMap[x][y];
	}
	
	public boolean isBuilderSelected(){
		return selectedUnitList.size() > 0;
	}
	
	private void generateMouvementMap(){
		if(mouvementMap == null){
			mouvementMap = new int[sizeX*5][sizeY*5];
		}
		for(int i=0; i<sizeX*5; i++){
			for(int j=0; j<sizeY*5; j++){
				int x = i/5;
				int y = j/5;
				mouvementMap[i][j] = constructionMap[x][y];
			}
		}
	}
	
	public void moveSelectedUnit(int x, int y){
		if(selectedUnitList.size() == 0){
			return;
		}
		generateMouvementMap();
		GridMap map = new GridMap(sizeX*5, sizeY*5);
		for(int i=0; i<sizeX*5; i++){
			for(int j=0; j<sizeY*5; j++){
				double n = mouvementMap[i][j];
				if(n == empty){
					n = 1;
				}else if(n == full){
					n = 10;
				}else if(n == tree){
					n = 2;
				}else if(n == farm){
					n = 2;
				}else if(n == road){
					n = 0.65;
				}
				map.set(i, j, n);
			}
		}
		for(Unit unit : selectedUnitList){
			int startX = unit.getX()/unitCellWidth;
			int startY = unit.getY()/unitCellWidth;
			int endX = x/unitCellWidth;
			int endY = y/unitCellWidth;
			GridLocation startLocation = new GridLocation(startX, startY, false, map.get(startX, startY));
			GridLocation endLocation = new GridLocation(endX, endY, true, map.get(endX, endY));
			GridPath path = pathfinding.getPath(startLocation, endLocation, map);
			unit.setPath(path);
		}
	}
	
	public GridPath getPath(Unit unit, Doodad doodad){
		int startX = unit.getX()/unitCellWidth;
		int startY = unit.getY()/unitCellWidth;
		int endX = doodad.getX()*5+2;
		int endY = doodad.getY()*5+2;
		GridPath path = getPath(startX, startY, endX, endY);
		return path;
	}
	
	public GridPath getPath(Unit unit, Building building){
		int startX = unit.getX()/unitCellWidth;
		int startY = unit.getY()/unitCellWidth;
		int endX;
		int endY;
		if(building instanceof City){
			endX = (building.getX()+2)*5+2;
			endY = (building.getY()+2)*5+2;
		}else{
			endX = (building.getX()+1)*5+2;
			endY = (building.getY()+2)*5+2;
		}
		GridPath path;
		if(unit instanceof Transporter){
			path = getPathTransporter(startX, startY, endX, endY);
		}else{
			path = getPath(startX, startY, endX, endY);
		}
		return path;
	}
	
	public GridPath getPathTransporter(int startX, int startY, int endX, int endY){
		generateMouvementMap();
		GridMap map = new GridMap(sizeX*5, sizeY*5);
		for(int i=0; i<sizeX*5; i++){
			for(int j=0; j<sizeY*5; j++){
				double n = mouvementMap[i][j];
				if(n == road){
					n = 1;
				}else if(n == city){
					n = 10;
				}else if(n == door){
					n = 2;
				}else if(n == door1){
					n = 2;
				}else{
					n = 10;
				}
				map.set(i, j, n);
			}
		}
		GridLocation startLocation = new GridLocation(startX, startY, false, map.get(startX, startY));
		GridLocation endLocation = new GridLocation(endX, endY, true, map.get(endX, endY));
		GridPath path = pathfinding.getPath(startLocation, endLocation, map);
		return path;
	}
	
	public GridPath getPath(int startX, int startY, int endX, int endY){
		generateMouvementMap();
		GridMap map = new GridMap(sizeX*5, sizeY*5);
		for(int i=0; i<sizeX*5; i++){
			for(int j=0; j<sizeY*5; j++){
				double n = mouvementMap[i][j];
				if(n == empty){
					n = 1;
				}else if(n == full){
					n = 10;
				}else if(n == tree){
					n = 2;
				}else if(n == farm){
					n = 2;
				}else if(n == road){
					n = 0.8;
				}else if(n == rock){
					n = 10;
				}else if(n == city){
					n = 10;
				}else if(n == door){
					n = 10;
				}else if(n == door1){
					n = 2;
				}
				map.set(i, j, n);
			}
		}
		GridLocation startLocation = new GridLocation(startX, startY, false, map.get(startX, startY));
		GridLocation endLocation = new GridLocation(endX, endY, true, map.get(endX, endY));
		GridPath path = pathfinding.getPath(startLocation, endLocation, map);
		return path;
	}
	
	public Tree getClosestTree(int x, int y){
		Tree result = null;
		int minDist = Integer.MAX_VALUE;
		for(int i=0; i<doodadList.size(); i++){
			Doodad doodad = doodadList.get(i);
			if(doodad instanceof Tree){
				Tree tree = (Tree)doodad;
				if(tree.isReadyToHarvest()){
					int treeX = tree.getX();
					int treeY = tree.getY();
					int dist = Math.abs(treeX-x) + Math.abs(treeY-y);
					if(dist < minDist){
						minDist = dist;
						result = tree;
					}
				}
			}
		}
		return result;
	}
	
	public Farm getClosestFarm(int x, int y){
		Farm result = null;
		int minDist = Integer.MAX_VALUE;
		for(int i=0; i<buildingList.size(); i++){
			Building building = buildingList.get(i);
			if(building instanceof Farm){
				Farm farm = (Farm)building;
				if(farm.isReadyToHarvest()){
					int treeX = farm.getX();
					int treeY = farm.getY();
					int dist = Math.abs(treeX-x) + Math.abs(treeY-y);
					if(dist < minDist){
						minDist = dist;
						result = farm;
					}
				}
			}
		}
		return result;
	}
	
	public void selectUnit(int x, int y){
		for(Unit unit : selectedUnitList){
			unit.setSelected(false);
		}
		selectedUnitList.clear();
		for(Unit unit : unitList){
			if(unit.contains(x, y)){
				selectedUnitList.add(unit);
				unit.setSelected(true);
			}
		}
	}
	
	public void selectBuilding(int x, int y){
		for(Building building : selectedBuildingList){
			building.setSelected(false);
		}
		selectedBuildingList.clear();
		for(Building building : buildingList){
			if(building.contains(x, y)){
				selectedBuildingList.add(building);
				building.setSelected(true);
			}
		}
	}
	
	private Building getBuilding(int x, int y){
		Building result = null;
		for(Building building : buildingList){
			if(building.contains(x, y)){
				result = building;
			}
		}
		return result;
	}
	
	public void tryToMakeLink(int x, int y){
		if(selectedBuildingList.size() == 0){
			return;
		}
		Building clickedBuilding = getBuilding(x, y);
		if(clickedBuilding == null || !(clickedBuilding instanceof City)){
			return;
		}
		City city = (City)clickedBuilding;
		for(int i=0; i<selectedBuildingList.size(); i++){
			if(selectedBuildingList.get(i) instanceof BuildingRessource){
				BuildingRessource building = (BuildingRessource)selectedBuildingList.get(i);
				city.setConnection(building);
			}
		}
	}
	
	public void addUnit(Unit unit){
		unitList.add(unit);
	}
	
	public void generateMap(){
		int posX = sizeX/2-2;
		int posY = sizeY/2-2;
		City city = new City(sizeX/2-2, sizeY/2-2);
		addBuilding(city);
		
		addDoodad(new Rock(posX+6, posY+4));
		addBuilding(new farmer.render.building.Mine(posX+6, posY+4));
		addBuilding(new farmer.render.building.LumberCamp(posX+7, posY-2));
		addBuilding(new farmer.render.building.Windmill(posX+2, posY-5));
		addBuilding(new farmer.render.building.Farm(posX-3, posY-5));
		addBuilding(new farmer.render.building.Farm(posX-3, posY-10));
		//Road to the lumbercamp
		addBuilding(new farmer.render.building.Road(posX+5, posY+2));
		addBuilding(new farmer.render.building.Road(posX+6, posY+2));
		addBuilding(new farmer.render.building.Road(posX+7, posY+2));
		addBuilding(new farmer.render.building.Road(posX+8, posY+2));
		addBuilding(new farmer.render.building.Road(posX+8, posY+1));
		//Road to the mine
		addBuilding(new farmer.render.building.Road(posX+2, posY+5));
		addBuilding(new farmer.render.building.Road(posX+2, posY+6));
		addBuilding(new farmer.render.building.Road(posX+2, posY+7));
		addBuilding(new farmer.render.building.Road(posX+3, posY+7));
		addBuilding(new farmer.render.building.Road(posX+4, posY+7));
		addBuilding(new farmer.render.building.Road(posX+5, posY+7));
		addBuilding(new farmer.render.building.Road(posX+6, posY+7));
		addBuilding(new farmer.render.building.Road(posX+7, posY+7));
		//Road to the windmill and farm
		addBuilding(new farmer.render.building.Road(posX+2, posY-1));
		addBuilding(new farmer.render.building.Road(posX+2, posY-2));
		addBuilding(new farmer.render.building.Road(posX+3, posY-2));
		addBuilding(new farmer.render.building.Road(posX+1, posY-2));
		addBuilding(new farmer.render.building.Road(posX+1, posY-3));
		addBuilding(new farmer.render.building.Road(posX+1, posY-4));
		addBuilding(new farmer.render.building.Road(posX+1, posY-5));
		addBuilding(new farmer.render.building.Road(posX+1, posY-6));
		addBuilding(new farmer.render.building.Road(posX+0, posY-6));
		addBuilding(new farmer.render.building.Road(posX-1, posY-6));
		//Place 3 mines
		for(int i=0; i<3; i++){
			int x = (int) (Math.random()*sizeX);
			int y = (int) (Math.random()*sizeY);
			if(isBuildable(x, y, 3)){
				Rock rock = new Rock(x, y);
				addDoodad(rock);
			}else{
				i--;
			}
		}
		//Place the forests
		for(int i=0; i<400; i++){
			int x = (int) (Math.random()*sizeX);
			int y = (int) (Math.random()*sizeY);
			if(isBuildable(x, y, 1) && !(contain(x, y, city))){
				Tree tree = new Tree(x, y);
				addDoodad(tree);
			}
		}
		Data.food = 0;
		Data.wood = 100;
		Data.rock = 50;
	}
	
	private boolean contain(int x, int y, City city){
		int badX = city.getX()-4;
		int badY = city.getY()-4;
		int farX = city.getX()+city.getSize()+4;
		int farY = city.getY()+city.getSize()+4;
		if(x >= badX && x<farX && y>=badY && y<farY){
			return true;
		}
		return false;
	}
	
	public void addBuilding(Building building){
		Data.wood -= building.getWoodCost();
		Data.rock -= building.getRockCost();
		buildingList.add(building);
		int x = building.getX();
		int y = building.getY();
		int size = building.getSize();
		for(int i=x; i<x+size; i++){
			for(int j=y; j<y+size; j++){
				if(building instanceof Farm){
					constructionMap[i][j] = farm;
				}else if(building instanceof Road){
					constructionMap[i][j] = road;
				}else if(building instanceof City){
					constructionMap[i][j] = city;
				}else{
					constructionMap[i][j] = full;
				}
			}
		}
		if(building instanceof BuildingRessource){
			theCity.setConnection((BuildingRessource) building);
		}
		if(building instanceof Windmill){
			Windmill windmill = (Windmill)building;
			windmill.placeBuilding();
			x += 1;
			y += 2;
			constructionMap[x][y] = door1;
		}else if(building instanceof LumberCamp){
			LumberCamp lumber = (LumberCamp)building;
			lumber.placeBuilding();
			x += 1;
			y += 2;
			constructionMap[x][y] = door1;
		}else if(building instanceof City){
			City city = (City)building;
			this.theCity = city;
			city.placeBuilding();
			x += 0;
			y += 2;
			constructionMap[x][y] = door;
			x += 1;
			constructionMap[x][y] = door;
			x += 1;
			constructionMap[x][y] = door;
			x += 1;
			constructionMap[x][y] = door;
			x += 1;
			constructionMap[x][y] = door;
			x = building.getX();
			y = building.getY();
			x += 2;
			y += 0;
			constructionMap[x][y] = door;
			y += 1;
			constructionMap[x][y] = door;
			y += 2;
			constructionMap[x][y] = door;
			y += 1;
			constructionMap[x][y] = door;
		}else if(building instanceof Mine){
			x += 1;
			y += 2;
			constructionMap[x][y] = door1;
		}
	}
	
	public void addDoodad(Doodad doodad){
		if(doodad instanceof Tree){
			Data.wood -= Tree.woodCost;
			Data.rock -= Tree.rockCost;
		}
		doodadList.add(doodad);
		int x = doodad.getX();
		int y = doodad.getY();
		int size = doodad.getSize();
		for(int i=x; i<x+size; i++){
			for(int j=y; j<y+size; j++){
				if(doodad instanceof Tree){
					constructionMap[i][j] = tree;
				}else if(doodad instanceof Rock){
					constructionMap[i][j] = rock;
				}else{
					constructionMap[i][j] = full;
				}
			}
		}
	}
	
	public boolean isBuildable(int x, int y, int size){
		//Check if it is in the map
		if(!(x>=0 && x+size<=sizeX && y>=0 && y+size<=sizeY)){
			return false;
		}
		boolean result = true;
		if(Data.building == Data.mine){
			//Check if the space is rock if we try to place a mine
			for(int i=x; i<x+size; i++){
				for(int j=y; j<y+size; j++){
					result &= (constructionMap[i][j]==rock);
				}
			}
		}else{
			//Check if the space is empty
			for(int i=x; i<x+size; i++){
				for(int j=y; j<y+size; j++){
					result &= (constructionMap[i][j]==empty);
				}
			}
		}
		return result;
	}
	
	public boolean hasBuilding(int x, int y){
		//Check if it is in the map
		if(!(x>=0 && x<=sizeX && y>=0 && y<=sizeY)){
			return false;
		}
		int n = constructionMap[x][y];
		if(n == tree){
			return true;
		}else if(n == full){
			return true;
		}else if(n == farm){
			return true;
		}else if(n == road){
			return true;
		}else if(n == door1){
			return true;
		}else{
			return false;
		}
	}
	
	public void removeUnit(Unit unit){
		unitList.remove(unit);
	}
	
	private void removeBuilding(Building building){
		buildingList.remove(building);
		int x = building.getX();
		int y = building.getY();
		int size = building.getSize();
		for(int i=x; i<x+size; i++){
			for(int j=y; j<y+size; j++){
				if(building instanceof Mine){
					constructionMap[i][j] = rock;
				}else{
					constructionMap[i][j] = empty;
				}
			}
		}
		if(building instanceof Windmill){
			Windmill windmill = (Windmill)building;
			windmill.destroy();
		}else if(building instanceof LumberCamp){
			LumberCamp lumber = (LumberCamp)building;
			lumber.destroy();
		}
	}
	
	private void removeDoodad(Doodad doodad){
		doodadList.remove(doodad);
		int x = doodad.getX();
		int y = doodad.getY();
		int size = doodad.getSize();
		for(int i=x; i<x+size; i++){
			for(int j=y; j<y+size; j++){
				constructionMap[i][j] = empty;
			}
		}
	}
	
	public void destroyBuilding(int x, int y){
		for(int i=0; i<buildingList.size(); i++){
			Building building = buildingList.get(i);
			int posX = building.getX();
			int posY = building.getY();
			int size = building.getSize();
			if(x >= posX && x < posX+size && y >= posY && y < posY+size){
				removeBuilding(building);
				return;
			}
		}
		for(int i=0; i<doodadList.size(); i++){
			Doodad doodad = doodadList.get(i);
			if(doodad instanceof Tree){
				int posX = doodad.getX();
				int posY = doodad.getY();
				if(x == posX && y == posY){
					removeDoodad(doodad);
				}
			}
		}
	}
	
	public int getSizeBuilding(int x, int y){
		for(int i=0; i<buildingList.size(); i++){
			Building building = buildingList.get(i);
			int posX = building.getX();
			int posY = building.getY();
			int size = building.getSize();
			if(x >= posX && x < posX+size && y >= posY && y < posY+size){
				return building.getSize();
			}
		}
		for(int i=0; i<doodadList.size(); i++){
			Doodad doodad = doodadList.get(i);
			int posX = doodad.getX();
			int posY = doodad.getY();
			if(x == posX && y == posY){
				return doodad.getSize();
			}
		}
		//Should never reach here
		return 1;
	}
	
	public int getPosXBuilding(int x, int y){
		for(int i=0; i<buildingList.size(); i++){
			Building building = buildingList.get(i);
			int posX = building.getX();
			int posY = building.getY();
			int size = building.getSize();
			if(x >= posX && x < posX+size && y >= posY && y < posY+size){
				return building.getX();
			}
		}
		for(int i=0; i<doodadList.size(); i++){
			Doodad doodad = doodadList.get(i);
			int posX = doodad.getX();
			int posY = doodad.getY();
			if(x == posX && y == posY){
				return doodad.getX();
			}
		}
		//Should never reach here
		return 1;
	}
	
	public int getPosYBuilding(int x, int y){
		for(int i=0; i<buildingList.size(); i++){
			Building building = buildingList.get(i);
			int posX = building.getX();
			int posY = building.getY();
			int size = building.getSize();
			if(x >= posX && x < posX+size && y >= posY && y < posY+size){
				return building.getY();
			}
		}
		for(int i=0; i<doodadList.size(); i++){
			Doodad doodad = doodadList.get(i);
			int posX = doodad.getX();
			int posY = doodad.getY();
			if(x == posX && y == posY){
				return doodad.getY();
			}
		}
		//Should never reach here
		return 1;
	}
	
	public int getTerrain(int x, int y){
		try{
			return terrainMap[x][y];
		}catch(Exception e){
			return -1;
		}
	}
	
	public int getSizeX(){
		return sizeX;
	}
	
	public int getSizeY(){
		return sizeY;
	}

	@Override
	public void update(int deltaTime) {
		for(Unit unit : unitList){
			unit.update(deltaTime);
		}
		for(Doodad doodad : doodadList){
			doodad.update(deltaTime);
		}
		for(Building building : buildingList){
			building.update(deltaTime);
		}
	}

}
